package hu.mapro.rollerx.core;

import hu.mapro.rollerx.core.RollerWorld.ContactMember;
import hu.mapro.rollerx.core.RollerWorld.UserData;

import java.util.ArrayList;
import java.util.List;

import org.poly2tri.Poly2Tri;
import org.poly2tri.geometry.polygon.Polygon;
import org.poly2tri.geometry.polygon.PolygonPoint;
import org.poly2tri.triangulation.TriangulationPoint;
import org.poly2tri.triangulation.delaunay.DelaunayTriangle;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.Texture.TextureWrap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.PolygonRegion;
import com.badlogic.gdx.graphics.g2d.PolygonSpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.BodyDef;
import com.badlogic.gdx.physics.box2d.BodyDef.BodyType;
import com.badlogic.gdx.physics.box2d.ChainShape;
import com.badlogic.gdx.physics.box2d.Contact;
import com.badlogic.gdx.physics.box2d.FixtureDef;
import com.badlogic.gdx.physics.box2d.World;

public abstract class AbstractGround implements Ground {

	public Body groundBody;
	protected UserData groundUserData;
	
	boolean loop = true;
	boolean cave = false;
	
	public AbstractGround() {
		super();
	}
	
	public AbstractGround(boolean loop) {
		super();
		this.loop = loop;
	}
	
	public AbstractGround(boolean loop, boolean cave) {
		super();
		this.loop = loop;
		this.cave = cave;
	}

	PolygonSpriteBatch polyBatch;
	private PolygonRegion polygonRegion;

	@Override
	public void init(final RollerWorld rollerWorld, float carWidth, float carHeight) {
		World world = rollerWorld.world;
		
		BodyDef groundBodyDef =new BodyDef();
		groundBodyDef.type = BodyType.StaticBody;
		
		
		groundBody = world.createBody(groundBodyDef);
		
		FixtureDef groundFixtureDef = new FixtureDef();
		groundFixtureDef.density = 0;
		groundFixtureDef.friction = 1.0f;
		groundFixtureDef.filter.categoryBits = RollerWorld.COLLISION_GROUND;
		groundFixtureDef.filter.maskBits = RollerWorld.COLLISION_CAR | RollerWorld.COLLISION_BROKEN_CARGO | RollerWorld.COLLISION_CARGO_GROUND_SENSOR;
		
		
		loop: while(true) {
			Vector2[][] polygons = generateVertices(carWidth, carHeight);
			for (int i=0;i<polygons.length;i++) {
				for (int j=0;j<polygons[i].length-1;j++) {
					if (polygons[i][j].dst2(polygons[i][j+1])<=0.005f*0.005f) continue loop;
				}
			}
			
			for (Vector2[] vecs : polygons) {
				ChainShape groundShape = new ChainShape();
				if (loop) {
					groundShape.createLoop(vecs);
				} else {
					groundShape.createChain(vecs);
				}
				groundFixtureDef.shape = groundShape;
				groundBody.createFixture(groundFixtureDef);
				groundShape.dispose();
			}
			
			if (loop) {
				
				int polygonIdx = 0;
				
				Polygon polygon = null;
				
				for (Vector2[] vecs : polygons) {
					
					List<PolygonPoint> points = new ArrayList<PolygonPoint>();
					for (Vector2 v : vecs) {
						points.add(new PolygonPoint(v.x, v.y));
					}
					Polygon p = new Polygon(points);
					
					if (polygonIdx==0) {
						polygon = p;
					} else {
						polygon.addHole(p);
					}
					
					polygonIdx++;
				}
				
				Poly2Tri.triangulate(polygon);

				int idx;
				List<TriangulationPoint> points = new ArrayList<TriangulationPoint>();
				
				short[] triangles = new short[polygon.getTriangles().size()*3];
				idx = 0;
				for (DelaunayTriangle triangle : polygon.getTriangles()) {
					triangles[idx++] = pointIndex(points, triangle.points[0]);
					triangles[idx++] = pointIndex(points, triangle.points[1]);
					triangles[idx++] = pointIndex(points, triangle.points[2]);
				}
				
				idx = 0;
				float[] vertices = new float[points.size()*2];
				for (TriangulationPoint p : points) {
					vertices[idx++]=p.getXf();
					vertices[idx++]=p.getYf();
				}

				
				
				if (cave) {
					Pixmap pixmap = new Pixmap(1, 1, Format.RGBA8888);
					pixmap.setColor(Color.BLACK);
					pixmap.drawPixel(0, 0);
					TextureRegion textureRegion = new TextureRegion(new Texture(pixmap));
					polygonRegion = new PolygonRegion(textureRegion, vertices, triangles);
				} else {
					Pixmap pixmap = new Pixmap(Gdx.files.internal("libgdx-logo.png"));
					Texture texture = new Texture(pixmap);
					texture.setWrap(TextureWrap.Repeat, TextureWrap.Repeat);
					TextureRegion textureRegion = new TextureRegion(texture);
					polygonRegion = new PolygonRegion(textureRegion, vertices, triangles);
					Grounds.textureMap(polygonRegion, 1/25f);
				}
				
			}
			
			
			break;
		}
		
		groundUserData = new UserData() {
			@Override
			public void contact(UserData contactedUserData, Contact contact, ContactMember contactedMember) {
				contactedUserData.contactGround(contact, ContactMember.A);
			}

			@Override
			public void contactGround(Contact contact, ContactMember a) {
			}

			@Override
			public void contactCargo(Contact contact, ContactMember contactedMember) {
				rollerWorld.cargoHitGround(contactedMember.thisFixture(contact));
			}
		};
		groundBody.setUserData(groundUserData);
		
		polyBatch = new PolygonSpriteBatch();
	}
	
	private static final short pointIndex(List<TriangulationPoint> points, TriangulationPoint point) {
		int idx = points.indexOf(point);
		if (idx==-1) {
			idx = points.size();
			points.add(point);
		}
		return (short) idx;
	}
	
	abstract Vector2[][] generateVertices(float carWidth, float carHeight);

	@Override
	public void dispose() {
	}
	
	@Override
	public void render(Camera camera) {
		if (loop) {
			if (cave) {
				Gdx.gl.glClearColor(Color.GRAY.r, Color.GRAY.g, Color.GRAY.b, Color.GRAY.a);
				Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
			}
			
			polyBatch.setProjectionMatrix(camera.combined);
			polyBatch.begin();
			polyBatch.draw(polygonRegion, 0, 0);
			polyBatch.end();
		}
	}
	
}
